package com.lmh.friend.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import com.lmh.common.core.constants.CacheConstants;
import com.lmh.common.core.constants.Constants;
import com.lmh.common.core.domain.TableDataInfo;
import com.lmh.common.core.enums.ExamListType;
import com.lmh.common.core.enums.ResultCode;
import com.lmh.common.redis.service.RedisService;
import com.lmh.common.security.exception.ServiceException;
import com.lmh.friend.domain.exam.ExamQuestion;
import com.lmh.friend.domain.exam.Exam;
import com.lmh.friend.domain.exam.dto.ExamQueryDTO;
import com.lmh.friend.domain.exam.dto.ExamRankDTO;
import com.lmh.friend.domain.exam.vo.ExamRankVO;
import com.lmh.friend.domain.exam.vo.ExamVO;
import com.lmh.friend.domain.user.UserExam;
import com.lmh.friend.mapper.exam.ExamMapper;
import com.lmh.friend.mapper.exam.ExamQuestionMapper;
import com.lmh.friend.mapper.user.UserExamMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class ExamCacheManager {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private RedisService redisService;

    public Long getListSize(Integer examListType, Long userId) {
        String examListKey = getExamListKey(examListType, userId);
        return redisService.getListSize(examListKey);
    }


    public Long getExamQuestionListSize(Long examId) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        return redisService.getListSize(examQuestionListKey);
    }

    public Long getRankListSize(Long examId) {
        return redisService.getListSize(getExamRankListKey(examId));
    }

    public TableDataInfo getExamVOList(ExamQueryDTO examQueryDTO, Long userId) {
        int start = (examQueryDTO.getPageNum() - 1) * examQueryDTO.getPageSize();
        int end = start + examQueryDTO.getPageSize() - 1; //下标需要 -1
        List<ExamVO> examVOList = new ArrayList<>();
        String key1 = null;
        String key2 = null;
        String key3 = null;
        if (examQueryDTO.getStartTime() == null || examQueryDTO.getStartTime().isEmpty()) {
            String examListKey = getExamListKey(examQueryDTO.getType(), userId);
            // 根据前端传来的分页信息获取指定区间的信息
            List<Long> examIdList = redisService.getCacheListByRange(examListKey, start, end, Long.class);
            // 根据列表中存储的examId，然后去查询竞赛的详细信息
            examVOList = assembleExamVOList(examIdList);
            if (CollectionUtil.isEmpty(examVOList)) {
                //说明redis中数据可能有问题 从数据库中查数据并且重新刷新缓存
                examVOList = getExamListByDB(examQueryDTO, userId); //从数据库中获取数据
                refreshCache(examQueryDTO.getType(), userId);
            }
            return TableDataInfo.success(examVOList,getListSize(examQueryDTO.getType(),userId));
        }else {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date startTime = dateFormat.parse(examQueryDTO.getStartTime());
                Date endTime = dateFormat.parse(examQueryDTO.getEndTime());
                String[] setKeys = getExamSetListKey(examQueryDTO.getType());
                key1 = CacheConstants.TMP_ZSET_KEY1 + UUID.randomUUID();
                key2 = CacheConstants.TMP_ZSET_KEY2 + UUID.randomUUID();
                key3 = CacheConstants.TMP_ZSET_KEY3 + UUID.randomUUID();
                long startTimeLong = startTime.getTime();
                long endTimeLong = endTime.getTime();
                redisService.setRangeZSet(setKeys[0],key1,startTimeLong,endTimeLong);
                redisService.setRangeZSet(setKeys[1],key2,startTimeLong,endTimeLong);
                redisService.getIntersect(key1,key2,key3);
//                // 直接在内存中对其进行交集操作
//                startSet.retainAll(endSet);
                examVOList = assembleExamVOSet(redisService.getRangeZSetList(key3,startTimeLong,endTimeLong
                        ,start,examQueryDTO.getPageSize()));
                if (CollectionUtil.isEmpty(examVOList)) {
                    //说明redis中数据可能有问题 从数据库中查数据并且重新刷新缓存
                    examVOList = getExamListByDB(examQueryDTO, userId); //从数据库中获取数据
                    refreshCache(examQueryDTO.getType(), userId);
                }
                return TableDataInfo.success(examVOList,redisService.getZSetSize(key3));
            } catch (ParseException e) {
                throw new RuntimeException(e);
            } finally {
                redisService.multiDeleteObject(Arrays.asList(key1,key2,key3));
            }
        }
    }

    private List<ExamVO> assembleExamVOList(List<Long> examIdList) {
        if (CollectionUtil.isEmpty(examIdList)) {
            //说明redis当中没数据 从数据库中查数据并且重新刷新缓存
            return null;
        }
        //拼接redis当中key的方法 并且将拼接好的key存储到一个list中
        List<String> detailKeyList = new ArrayList<>();
        for (Long examId : examIdList) {
            detailKeyList.add(getDetailKey(examId));
        }
        List<ExamVO> examVOList = redisService.multiGet(detailKeyList, ExamVO.class);
        CollUtil.removeNull(examVOList);
        if (CollectionUtil.isEmpty(examVOList) || examVOList.size() != examIdList.size()) {
            //说明redis中数据有问题 从数据库中查数据并且重新刷新缓存
            return null;
        }
        return examVOList;
    }

    private List<ExamVO> assembleExamVOSet(Set<String> result) {
        if (CollectionUtil.isEmpty(result)) {
            //说明redis当中没数据 从数据库中查数据并且重新刷新缓存
            return null;
        }
        //拼接redis当中key的方法 并且将拼接好的key存储到一个list中
        List<String> detailKeyList = new ArrayList<>();
        for (String examId : result) {
            detailKeyList.add(getDetailKey(examId));
        }
        List<ExamVO> examVOList = redisService.multiGet(detailKeyList, ExamVO.class);
        CollUtil.removeNull(examVOList);
        if (CollectionUtil.isEmpty(examVOList) || examVOList.size() != result.size()) {
            //说明redis中数据有问题 从数据库中查数据并且重新刷新缓存
            return null;
        }
        return examVOList;
    }

    public List<ExamRankVO> getExamRankList(ExamRankDTO examRankDTO) {
        int start = (examRankDTO.getPageNum() - 1) * examRankDTO.getPageSize();
        int end = start + examRankDTO.getPageSize() - 1; //下标需要 -1
        return redisService.getCacheListByRange(getExamRankListKey(examRankDTO.getExamId())
                                        , start, end, ExamRankVO.class);
    }

    public List<Long> getAllUserExamList(Long userId) {
        String examListKey = CacheConstants.USER_EXAM_LIST + userId;
        List<Long> userExamIdList = redisService.getCacheListByRange(examListKey, 0, -1, Long.class);
        if (CollectionUtil.isNotEmpty(userExamIdList)) {
            return userExamIdList;
        }
        List<UserExam> userExamList =
                userExamMapper.selectList(new LambdaQueryWrapper<UserExam>().eq(UserExam::getUserId, userId));
        if (CollectionUtil.isEmpty(userExamList)) {
            return null;
        }
        refreshCache(ExamListType.USER_EXAM_LIST.getValue(), userId);
        return userExamList.stream().map(UserExam::getExamId).collect(Collectors.toList());
    }

    public void addUserExamCache(Long userId, Long examId) {
        String userExamListKey = getUserExamListKey(userId);
        redisService.leftPushForList(userExamListKey, examId);
    }

    public Long getFirstQuestion(Long examId) {
        return redisService.indexForList(getExamQuestionListKey(examId), 0, Long.class);
    }

    public Long preQuestion(Long examId, Long questionId) {
        Long index = redisService.indexOfForList(getExamQuestionListKey(examId), questionId);
        if (index == 0) {
            throw new ServiceException(ResultCode.FAILED_FIRST_QUESTION);
        }
        return redisService.indexForList(getExamQuestionListKey(examId), index - 1, Long.class);
    }

    public Long nextQuestion(Long examId, Long questionId) {
        Long index = redisService.indexOfForList(getExamQuestionListKey(examId), questionId);
        long lastIndex = getExamQuestionListSize(examId) - 1;
        if (index == lastIndex) {
            throw new ServiceException(ResultCode.FAILED_LAST_QUESTION);
        }
        return redisService.indexForList(getExamQuestionListKey(examId), index + 1, Long.class);
    }

    //刷新缓存逻辑
    public void refreshCache(Integer examListType, Long userId) {
        List<Exam> examList = new ArrayList<>();
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            //查询未完赛的竞赛列表
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .gt(Exam::getEndTime, LocalDateTime.now()) // 表示竞赛结束时间晚于当前时间的竞赛
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            //查询历史竞赛
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .le(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        } else if (ExamListType.USER_EXAM_LIST.getValue().equals(examListType)) {
            List<ExamVO> examVOList = userExamMapper.selectUserExamList(userId);
            examList = BeanUtil.copyToList(examVOList, Exam.class);
        }
        // 如果从MySQL中查询的examList还是为空的话就表示，当前查询的竞赛为空，直接返回
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        examListToCache(examListType, userId, examList);
    }

    private void examListToCache(Integer examListType, Long userId, List<Exam> examList) {
        // 将每一个竞赛都存储在redis缓存中
        Map<String, Exam> examMap = new HashMap<>();
        // redis中存储的未完赛的或者历史竞赛，都是存储的是竞赛的examId
        List<Long> examIdList = new ArrayList<>();
        // 将竞赛的开始结束时间作为score，examId作为value存储进set中
        Set<ZSetOperations.TypedTuple<String>> startSet = new HashSet<>();
        Set<ZSetOperations.TypedTuple<String>> endtSet = new HashSet<>();
        for (Exam exam : examList) {
            Long examId = exam.getExamId();
            examMap.put(getDetailKey(examId), exam);
            examIdList.add(examId);
            // 转换时区（假设时间为UTC）
            long startTime = exam.getStartTime().toInstant(ZoneOffset.UTC).toEpochMilli();
            long endTime = exam.getEndTime().toInstant(ZoneOffset.UTC).toEpochMilli();
            // 将开始时间添加到 startSet
            startSet.add(ZSetOperations.TypedTuple.of(String.valueOf(examId), (double) startTime));
            endtSet.add(ZSetOperations.TypedTuple.of(String.valueOf(examId), (double) endTime));
        }
        // 因为redis中存储竞赛信息使用的是string类型，直接set，可以覆盖掉之前的内容
        redisService.multiSet(examMap);  //刷新详情缓存
        // 而未完赛或者历史竞赛在redis中是使用列表的形式存储的，如果不对其先进行清空的话，就会导致列表中出现重复的examId
        // 那么这时就会问了，执行refreshCache()方法的前提条件不是在redis中查询竞赛列表的结果未空吗，这里为啥还需要这一步操作呢
        // 即使在Redis中查询竞赛列表的结果为空，也可能存在旧的缓存数据。为了避免数据重复，需要在更新前清空旧的列表缓存。
        redisService.deleteObject(getExamListKey(examListType, userId));
        redisService.rightPushAll(getExamListKey(examListType, userId), examIdList);      //刷新列表缓存
        String[] setKeys = getExamSetListKey(examListType);
        redisService.deleteObject(setKeys[0]);
        redisService.deleteObject(setKeys[1]);
        redisService.multiAddZSetObject(setKeys[0],startSet);
        redisService.multiAddZSetObject(setKeys[1],endtSet);
        // 将用户的竞赛信息id存储在redis中
        if (ExamListType.USER_EXAM_LIST.getValue().equals(examListType)) {
            redisService.rightPushAll(getUserExamListKey(userId),examIdList);
        }
    }

    private String[] getExamSetListKey(Integer type) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(type)) {
            return new String[]{CacheConstants.EXAM_CONTINUE_START_TIME
                    ,CacheConstants.EXAM_CONTINUE_END_TIME};
        }else {
            return new String[]{CacheConstants.EXAM_HISTORY_START_TIME
                    ,CacheConstants.EXAM_HISTORY_END_TIME};
        }
    }

    public void refreshExamQuestionCache(Long examId) {
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if (CollectionUtil.isEmpty(examQuestionList)) {
            return;
        }
        List<Long> examQuestionIdList = examQuestionList.stream()
                .map(ExamQuestion::getQuestionId)
                .collect(Collectors.toList());
        redisService.rightPushAll(getExamQuestionListKey(examId), examQuestionIdList);
        //节省 redis缓存资源
        long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
        redisService.expire(getExamQuestionListKey(examId), seconds, TimeUnit.SECONDS);
    }

    public Long getExamFirstQuestion(Long examId) {
        return redisService.indexForList(getExamQuestionListKey(examId),0, Long.class);
    }


    public void refreshExamRankCache(Long examId) {
        List<ExamRankVO> examRankVOList = userExamMapper.selectExamRankList(examId);
        if (CollectionUtil.isEmpty(examRankVOList)) {
            return;
        }
        redisService.rightPushAll(getExamRankListKey(examId), examRankVOList);
    }

    private List<ExamVO> getExamListByDB(ExamQueryDTO examQueryDTO, Long userId) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        if (ExamListType.USER_EXAM_LIST.getValue().equals(examQueryDTO.getType())) {
            //查询我的竞赛列表
            return userExamMapper.selectUserExamList(userId);
        } else {
            //查询C端的竞赛列表
            return examMapper.selectExamList(examQueryDTO);
        }
    }

    private String getExamListKey(Integer examListType, Long userId) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_UNFINISHED_LIST;
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_HISTORY_LIST;
        } else {
            return CacheConstants.USER_EXAM_LIST + userId;
        }
    }

    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    private String getDetailKey(String examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    private String getUserExamListKey(Long userId) {
        return CacheConstants.USER_EXAM_LIST + userId;
    }

    private String getExamQuestionListKey(Long examId) {
        return CacheConstants.EXAM_QUESTION_LIST + examId;
    }

    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST + examId;
    }
}
